package cn.pedant.SafeWebViewBridge;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import ohos.agp.components.webengine.WebView;
import ohos.agp.utils.TextTool;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;

public class JsCallJava {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0x00020, "JsCallJava");
    private static final String RETURN_RESULT_FORMAT = "{\"code\": %d, \"result\": %s}";
    private static final int NUM_10 = 10;
    private static final int NUM_500 = 500;
    private static final int NUM_200 = 200;
    private HashMap<String, Method> mMethodsMap;
    private String mInjectedName;
    private String mPreInterfaceJs;
    private Gson mGson;

    /**
     * 构造
     *
     * @param injectedName 类名
     * @param injectedCls 类
     */
    public JsCallJava(String injectedName, Class injectedCls) {
        try {
            if (TextTool.isNullOrEmpty(injectedName)) {
                throw new Exception("injected name can not be null");
            }
            mInjectedName = injectedName;
            mMethodsMap = new HashMap<String, Method>();
            /**
             * 获取自身声明的所有方法（包括public private protected）， getMethods会获得所有继承与非继承的方法
             */
            Method[] methods = injectedCls.getDeclaredMethods();
            StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\"");
            sb.append(mInjectedName);
            sb.append(" initialization begin\");"
                    + "var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0)"
                    + ";var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};");
            for (Method method : methods) {
                String sign;
                if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC)
                        || (sign = genJavaMethodSign(method)) == null) {
                    continue;
                }
                mMethodsMap.put(sign, method);
                sb.append(String.format("a.%s=", method.getName()));
            }

            sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\"");
            sb.append(mInjectedName);
            sb.append(" call error, message:miss method name\"}"
                    + "var e=[];for(var h=1;h<f.length;h++){var c=f[h];var j=typeof c;e[e.length]=j;if(j==\"function\")"
                    + "{var d=a.queue.length;a.queue[d]=c;f[h]=d}}"
                    + "var g=JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f})));"
                    + "if(g.code!=200){throw\"");
            sb.append(mInjectedName);
            sb.append(" call error, code:\"+g.code+\", message:\"+g.result}"
                    + "return g.result};Object.getOwnPropertyNames(a).forEach(function(d)"
                    + "{var c=a[d];if(typeof c===\"function\"&&d!==\"callback\")"
                    + "{a[d]=function(){return c.apply(a,[d].concat(Array.prototype.slice.call(arguments,0)))}}});b.");
            sb.append(mInjectedName);
            sb.append("=a;console.log(\"");
            sb.append(mInjectedName);
            sb.append(" initialization end\")})(window);");
            mPreInterfaceJs = sb.toString();
        } catch (Exception e) {
            HiLog.info(TAG, "init js error:" + e.getMessage());
        }
    }

    private String genJavaMethodSign(Method method) {
        StringBuilder sign = new StringBuilder(method.getName());
        Class[] argsTypes = method.getParameterTypes();
        int len = argsTypes.length;
        if (len < 1 || argsTypes[0] != WebView.class) {
            HiLog.info(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
            return null;
        }
        for (int kk = 1; kk < len; kk++) {
            Class cls = argsTypes[kk];
            if (cls == String.class) {
                sign.append("_S");
            } else if (cls == int.class
                    || cls == long.class
                    || cls == float.class
                    || cls == double.class) {
                sign.append("_N");
            } else if (cls == boolean.class) {
                sign.append("_B");
            } else if (cls == JsonObject.class) {
                sign.append("_O");
            } else if (cls == JsCallback.class) {
                sign.append("_F");
            } else {
                sign.append("_P");
            }
        }
        return sign.toString();
    }

    /**
     * getpreloadinterfaceJs
     *
     * @return String
     */
    public String getpreloadinterfaceJs() {
        return mPreInterfaceJs;
    }

    /**
     * 回调
     *
     * @param webView 浏览器
     * @param jsonStr json
     * @return string
     */
    public String call(WebView webView, String jsonStr) {
        if (!TextTool.isNullOrEmpty(jsonStr)) {
            try {
                JsonObject callJson = new Gson().fromJson(jsonStr, JsonObject.class);

                String methodName = callJson.get("method").getAsString();
                JsonArray argsTypes = callJson.get("types").getAsJsonArray();
                JsonArray argsVals = callJson.get("args").getAsJsonArray();

                String sign = methodName;
                int len = argsTypes.size();
                Object[] values = new Object[len + 1];
                int numIndex = 0;
                String currType;

                values[0] = webView;

                for (int kk = 0; kk < len; kk++) {
                    currType = argsTypes.get(kk).getAsString();
                    if ("string".equals(currType)) {
                        sign += "_S";
                        values[kk + 1] = argsVals.get(kk).isJsonNull() ? null : argsVals.get(kk).getAsString();
                    } else if ("number".equals(currType)) {
                        sign += "_N";
                        numIndex = numIndex * NUM_10 + kk + 1;
                    } else if ("boolean".equals(currType)) {
                        sign += "_B";
                        values[kk + 1] = argsVals.get(kk).getAsBoolean();
                    } else if ("object".equals(currType)) {
                        sign += "_O";
                        values[kk + 1] = argsVals.get(kk).isJsonNull() ? null : argsVals.get(kk).getAsJsonObject();
                    } else if ("function".equals(currType)) {
                        sign += "_F";
                        values[kk + 1] = new JsCallback(webView, mInjectedName, argsVals.get(kk).getAsInt());
                    } else {
                        sign += "_P";
                    }
                }

                Method currMethod = mMethodsMap.get(sign);

                // 方法匹配失败
                if (currMethod == null) {
                    return getReturn(jsonStr, NUM_500, "not found method(" + sign + ") with valid parameters");
                }

                // 数字类型细分匹配
                if (numIndex > 0) {
                    Class[] methodTypes = currMethod.getParameterTypes();
                    int currIndex;
                    Class currCls;
                    while (numIndex > 0) {
                        currIndex = numIndex - numIndex / NUM_10 * NUM_10;
                        currCls = methodTypes[currIndex];
                        if (currCls == int.class) {
                            values[currIndex] = argsVals.get(currIndex - 1).getAsInt();
                        } else if (currCls == long.class) {
                            values[currIndex] = Long.parseLong(argsVals.get(currIndex - 1).getAsString());
                        } else {
                            values[currIndex] = argsVals.get(currIndex - 1).getAsDouble();
                        }
                        numIndex /= NUM_10;
                    }
                }
                return getReturn(jsonStr, NUM_200, currMethod.invoke(null, values));
            } catch (Exception e) {
                if (e.getCause() != null) {
                    return getReturn(jsonStr, NUM_500, "method execute error:" + e.getCause().getMessage());
                }
                return getReturn(jsonStr, NUM_500, "method execute error:" + e.getMessage());
            }
        } else {
            return getReturn(jsonStr, NUM_500, "call data empty");
        }
    }

    private String getReturn(String reqJson, int stateCode, Object result) {
        String insertRes;
        if (result == null) {
            insertRes = "null";
        } else if (result instanceof String) {
            result = ((String) result).replace("\"", "\\\"");
            insertRes = "\"" + result + "\"";
        } else if (!(result instanceof Integer)
                && !(result instanceof Long)
                && !(result instanceof Boolean)
                && !(result instanceof Float)
                && !(result instanceof Double)
                && !(result instanceof JsonObject)) {
            if (mGson == null) {
                mGson = new Gson();
            }
            insertRes = mGson.toJson(result);
        } else {
            insertRes = String.valueOf(result);
        }
        String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes);
        HiLog.info(TAG, mInjectedName + " call json: " + reqJson + " result:" + resStr);
        return resStr;
    }
}